﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Titan.Runtiming
{
    public class Runtime : IServiceProvider
    {
        private Runtime()
        {
            _services = new Dictionary<Type, List<object>>();
        }

        public void AddService(object service)
        {
            if (null == service)
                throw new ArgumentNullException("service");
            if (_isStarted)
                throw new InvalidOperationException(string.Format(SR.ServiceCanNotModifiedIfStart, "runtime"));

            lock (_services)
            {
                if (GetAllServices(service.GetType()).Contains(service))
                    throw new InvalidOperationException(SR.CanNotAddServiceTwice);

                //Supports lookup service by interface type.
                foreach (Type intfType in service.GetType().GetInterfaces())
                {
                    AddService(intfType, service);
                }

                for (Type type = service.GetType(); type != null; type = type.BaseType)
                {
                    AddService(type, service);
                }

                if (service is RuntimeService) ((RuntimeService)service).SetRuntime(this);
                OnServiceChanged(service, ServiceChangedAction.Add);
            }
        }

        // Must lock
        private void AddService(Type type, object service)
        {
            List<object> list;
            if (_services.TryGetValue(type, out list))
                list.Add(service);
            else
            {
                list = new List<object>();
                list.Add(service);
                _services.Add(type, list);
            }
        }

        public bool RemoveService(object service)
        {
            if (null == service)
                throw new ArgumentNullException("service");
            if (_isStarted)
                throw new InvalidOperationException(string.Format(SR.ServiceCanNotModifiedIfStart, "runtime"));

            bool exist = false;
            lock (_services)
            {
                foreach (List<object> list in _services.Values)
                {
                    if (list.Remove(service))
                        exist = true;
                }
            }

            if (service is RuntimeService && exist)
                ((RuntimeService)service).SetRuntime(null);

            OnServiceChanged(service, ServiceChangedAction.Remove);

            return exist;
        }

        private void OnServiceChanged(object service, ServiceChangedAction action)
        {
            if (_serviceChanged != null)
            {
                RuntimeServiceChangedEventArgs eventArgs = new RuntimeServiceChangedEventArgs(this, service, action);
                _serviceChanged(this, eventArgs);
            }
        }

        public object GetService(Type serviceType)
        {
            if (null == serviceType)
                throw new ArgumentNullException("serviceType");

            object retval = null;

            lock (_services)
            {
                List<object> list;
                if (_services.TryGetValue(serviceType, out list))
                {
                    if (list.Count > 1)
                        throw new InvalidCastException(string.Format(SR.MoreThanOneService, serviceType.ToString()));
                    if (list.Count == 1)
                        retval = list[0];
                }
            }

            return retval;
        }

        public T GetService<T>()
        {
            return (T)GetService(typeof(T));
        }

        public ReadOnlyCollection<object> GetAllServices(Type serviceType)
        {
            if (null == serviceType)
                throw new ArgumentNullException("serviceType");
            List<object> list = new List<object>();
            lock (_services)
            {
                if (_services.ContainsKey(serviceType))
                    list.AddRange(_services[serviceType]);
            }
            return new ReadOnlyCollection<object>(list);
        }

        public ReadOnlyCollection<T> GetAllServices<T>()
        {
            List<T> list = new List<T>();
            foreach (T service in GetAllServices(typeof(T)))
            {
                list.Add(service);
            }
            return new ReadOnlyCollection<T>(list);
        }

        public ReadOnlyCollection<object> GetAllServices()
        {
            return GetAllServices(typeof(object));
        }

        public void Start()
        {
            if (!_isStarted)
            {
                ReadOnlyCollection<RuntimeService> services = GetAllServices<RuntimeService>();
                int index = 0;
                while (index < services.Count)
                {
                    services[index].Start();
                    index++;
                }
                _isStarted = true;
                _startTime = DateTime.Now;
                if (null != _started)
                    _started(this, new RuntimeEventArgs(this));
            }
        }

        public void Stop()
        {
            if (_isStarted)
            {
                Exception error = null;
                ReadOnlyCollection<RuntimeService> services = GetAllServices<RuntimeService>();
                int index = 0;
                while (index < services.Count)
                {
                    try
                    {
                        services[index].Stop();
                    }
                    catch (Exception ex)
                    {
                        error = ex;
                    }
                    index++;
                }
                _isStarted = false;
                if (null != error)
                    throw error;
                if (null != _stoped)
                    _stoped(this, new RuntimeEventArgs(this));
            }
        }

        public bool IsStarted
        {
            get { return _isStarted; }
        }

        public DateTime StartTime
        {
            get { return _startTime; }
        }

        public event EventHandler<RuntimeEventArgs> Started
        {
            add { _started += value; }
            remove { _started -= value; }
        }

        public event EventHandler<RuntimeEventArgs> Stoped
        {
            add { _stoped += value; }
            remove { _stoped -= value; }
        }

        public event EventHandler<RuntimeServiceChangedEventArgs> ServiceChanged
        {
            add { _serviceChanged += value; }
            remove { _serviceChanged -= value; }
        }

        public static Runtime Default
        {
            get
            {
                if (null == _default)
                {
                    lock (_lock)
                    {
                        if (null == _default)
                            _default = new Runtime();
                    }
                }
                return _default;
            }
        }

        private DateTime _startTime;
        private bool _isStarted;
        private Dictionary<Type, List<object>> _services;
        private EventHandler<RuntimeEventArgs> _started;
        private EventHandler<RuntimeEventArgs> _stoped;
        private EventHandler<RuntimeServiceChangedEventArgs> _serviceChanged;
        private static Runtime _default;
        private static object _lock = new object();
    }
}
